package red.stu.pconstraint.config;

import org.jetbrains.annotations.NotNull;
import red.stu.pconstraint.dto.mapper.Branch;
import red.stu.pconstraint.dto.mapper.BranchNode;
import red.stu.pconstraint.dto.mapper.ProcessMapper;
import red.stu.pconstraint.dto.mapper.block.InterceptorBlock;
import red.stu.pconstraint.dto.mapper.block.BaseNodeBlock;
import red.stu.pconstraint.dto.mapper.block.WhenBlock;
import red.stu.pconstraint.dto.mapper.token.*;
import red.stu.pconstraint.xml.XMLConfigResolver;
import red.stu.pconstraint.xml.parser.XNode;
import red.stu.pconstraint.xml.parser.XPathParser;
import red.stu.pconstraint.xml.parser.exception.BuilderException;

import java.io.InputStream;
import java.io.Reader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @author yihrmc @stu.red
 * @date 2019/11/15 10:18
 */
public class XMLMapperBuilder extends BaseBuilder<ProcessMapper> {

    private boolean parsed;
    private XPathParser parser;
    private Configuration configuration;

    public XMLMapperBuilder(@NotNull Reader reader, @NotNull  Configuration configuration, boolean validation) {
        this(new XPathParser(reader, validation, configuration.getVariables(), new XMLConfigResolver()), configuration);
    }

    public XMLMapperBuilder(@NotNull InputStream inputStream, @NotNull Configuration configuration, boolean validation) {
        this(new XPathParser(inputStream, validation, configuration.getVariables(), new XMLConfigResolver()), configuration);
    }

    public XMLMapperBuilder(@NotNull XPathParser parser, @NotNull Configuration configuration) {
        super(new ProcessMapper());
        this.parser = parser;
        this.configuration = configuration;
    }

    public ProcessMapper parse() {
        if (parsed) {
            throw new BuilderException("Each XMLMapperBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/process"));
        return entity;
    }

    private void parseConfiguration(XNode root) {
        String namespace = root.getStringAttribute("namespace");
        if (namespace == null) {
            throw new BuilderException("Process namespace is null.");
        }
        entity.setNamespace(namespace);
        List<XNode> children = root.getChildren();
        for (XNode child : children) {
            String tagName = child.getName();
            if ("describe".equalsIgnoreCase(tagName)) {
                String describe = child.getStringBody();
                entity.setDescribe(describe);
            } else if ("branchs".equalsIgnoreCase(tagName)) {
                branchsElement(child);
            }
        }
    }

    private void branchsElement(XNode context) {
        List<XNode> children = context.getChildren();
        Map<String, Branch> branchs = entity.getBranchs();
        if (branchs == null) {
            branchs = new HashMap<>();
            entity.setBranchs(branchs);
        }
        for (XNode child : children) {
            String branchId = child.getStringAttribute("id");
            if (branchId == null) {
                String tagName = child.getName();
                if ("master".equalsIgnoreCase(tagName)) {
                    branchId = Branch.MASTER_ID;
                }
            }
            String branchDescribe = child.getStringAttribute("describe");

            Branch branch = new Branch()
                    .setId(branchId)
                    .setDescribe(branchDescribe);

            Map<String, BranchNode> branchNodes = nodesElement(child, branch);
            branch.setNodes(branchNodes);

            branchs.put(branchId, branch);
        }
    }

    private Map<String, BranchNode> nodesElement(XNode context, Branch branch) {
        Map<String, BranchNode> branchNodes = new HashMap<>();
        BranchNode previousNode = null;
        XNode[] children = context.getChildrenArray();
        for (int i = 0, len = children.length; i < len; i++) {
            XNode child = children[i];
            String id = child.getStringAttribute("id");
            if (id == null) {
                throw new BuilderException("Node id is null: " + context.toString());
            }
            String text = child.getStringAttribute("text");
            List<BaseNodeBlock> nodeBlocks = blocksElement(child);

            boolean isFirst = (i == 0);
            boolean isLast = (i == len - 1);
            BranchNode branchNode = new BranchNode()
                    .setId(id)
                    .setText(text)
                    .setBlocks(nodeBlocks)
                    .setFirst(isFirst)
                    .setLast(isLast);

            if (isFirst) {
                branch.setFirstNode(branchNode);
            }
            if (isLast) {
                branch.setLastNode(branchNode);
            }

            // add Chain
            if (previousNode != null) {
                previousNode.setNextBranchNode(branchNode);
                branchNode.setPreviousBranchNode(previousNode);
            }
            previousNode = branchNode;

            branchNodes.put(id, branchNode);
        }

//        List<XNode> children = context.getChildren();
//        for (XNode child : children) {
//            String id = child.getStringAttribute("id");
//            if (id == null) {
//                throw new BuilderException("Node id is null: " + context.toString());
//            }
//            String text = child.getStringAttribute("text");
//            List<BaseNodeBlock> nodeBlocks = blocksElement(child);
//
//            BranchNode branchNode = new BranchNode()
//                    .setId(id)
//                    .setText(text)
//                    .setBlocks(nodeBlocks);
//            if (masterNode == null) {
//                masterNode = branchNode;
//            }
//            if (previousNode != null) {
//                previousNode.setNextBranchNode(branchNode);
//            }
//            previousNode = branchNode;
//
//            branchNodes.put(id, branchNode);
//        }
        return branchNodes;
    }

    private List<BaseNodeBlock> blocksElement(XNode context) {
        List<BaseNodeBlock> nodeBlocks = new LinkedList<>();
        XNode[] childrenArray = context.getChildrenArray();
        for (int i = 0, len = childrenArray.length; i < len; i++) {
            XNode child = childrenArray[i];
            String blockType = child.getName();
            BaseNodeBlock block;
            if (WhenBlock.BLOCK_TYPE.equalsIgnoreCase(blockType)) {
                String stateName = child.getStringAttribute("state");
                block = new WhenBlock()
                        .setStateName(stateName);

            } else if (InterceptorBlock.BLOCK_TYPE.equalsIgnoreCase(blockType)) {
                String path = child.getStringAttribute("path");
                block = new InterceptorBlock().setPath(path);
            } else {
                throw new BuilderException("NodeBlock type<" + blockType + "> not found.");
            }

            String text = child.getStringAttribute("text");
            List<BlockExpression> expressions = tokensElement(child);
            block.setText(text).setExpressions(expressions);

            block.setIndex(i);

            nodeBlocks.add(block);
        }
        return nodeBlocks;
    }

    private List<BlockExpression> tokensElement(XNode context) {
        List<BlockExpression> tokens = new LinkedList<>();
        List<XNode> children = context.getChildren();
        for (XNode child : children) {
            String expressionType = child.getName();
            BlockExpression blockExpression;
            if (BackExpression.TYPE.equalsIgnoreCase(expressionType)) {
                String branchId = child.getStringAttribute("branch");
                String nodeId = child.getStringAttribute("node");
                String stateName = null;
                List<XNode> changeBranchNodes = child.getChildren();
                if (changeBranchNodes.size() > 0) {
                    if (changeBranchNodes.size() > 1) {
                        throw new BuilderException("changeBranch Too many: " + child.toString());
                    }
                    stateName = changeBranchNodes.get(0).getStringAttribute("stateName");
                }
                blockExpression = new BackExpression()
                        .setBranchId(branchId)
                        .setNodeId(nodeId)
                        .setStateName(stateName);

            } else if (CloseExpression.TYPE.equalsIgnoreCase(expressionType)) {
                String name = child.getStringAttribute("name");
                // String message = child.getStringBody();
                blockExpression = new CloseExpression()
                        .setName(name);

            } else if (CodeExpression.TYPE.equalsIgnoreCase(expressionType)) {
                String method = child.getStringAttribute("method");
                blockExpression = new CodeExpression()
                        .setMethod(method);

            } else if (GotoExpression.TYPE.equalsIgnoreCase(expressionType)) {
                String branchId = child.getStringAttribute("branch");
                String nodeId = child.getStringAttribute("node");
                String stateName = null;
                List<XNode> changeBranchNodes = child.getChildren();
                if (changeBranchNodes.size() > 0) {
                    if (changeBranchNodes.size() > 1) {
                        throw new BuilderException("changeBranch Too many: " + child.toString());
                    }
                    stateName = changeBranchNodes.get(0).getStringAttribute("stateName");
                }
                blockExpression = new GotoExpression()
                        .setBranchId(branchId)
                        .setNodeId(nodeId)
                        .setStateName(stateName);

            } else if (PassExpression.TYPE.equalsIgnoreCase(expressionType)) {
                String name = child.getStringAttribute("name");
                blockExpression = new PassExpression()
                        .setName(name);

            } else {
                throw new BuilderException("BlockExpression type<" + expressionType + "> not found");
            }

            String text = child.getStringAttribute("text");
            tokens.add(blockExpression
                    .setText(text));
        }

        return tokens;
    }

}
